﻿using System;
using System.IO;
using System.Reflection;
using System.ComponentModel;
using System.Collections.Generic;
using Dokan;

namespace RFServer.Drive
{
    class Mounter
    {
        public static string MountPoint { get; private set; }
        public static char DriveLetter { get; private set; }
        public static bool Success { get; private set; }

        /// <summary>
        /// Монтирование виртуального диска в зависимости от настроек,
        /// указанных в классе Setup
        /// </summary>

        public static void Automount()
        {
            if (Setup.Drive)
                if (Setup.DriveLetterSpecified)
                    Mount(Setup.DriveLetter);
                else
                    Mount();
        }

        /// <summary>
        /// Монтирование виртуального диска на определенную букву диска
        /// </summary>
        /// <param name="driveLetter">Буква диска</param>

        public static void Mount(char driveLetter)
        {
            DriveLetter = driveLetter;

            mount();
        }

        /// <summary>
        /// Монтирование виртуального диска на последнюю неиспользованную
        /// букву диска
        /// </summary>

        public static void Mount()
        {
            try
            {
                DriveLetter = getFirstAviableDriveLetter();

                mount();
            }
            catch (DriveNotFoundException)
            {
                Setup.Log("[drive]: No drive letter is available");
                Success = false;
            }
        }

        /// <summary>
        /// Функция монтирует виртуальную файловую систему RFS на указанную
        /// букву диска DriveLetter
        /// </summary>

        private static void mount()
        {
            /* Если .NET библиотека Dokan не найдена, то сообщаем об ошибке */

            if (!File.Exists("DokanNet.dll"))
            {
                Setup.Log("[drive]: Assembley DokanNet.dll not found");
                Success = false;
                return; 
            }


            /* Устанавливаем букву диска */

            Setup.Log("[drive]: Mounting on " + DriveLetter + ": ");

            MountPoint = DriveLetter + ":\\";

            /* По умолчанию, операция монтирования прошла успешно */

            Success = true;

            /* Монтируем виртуальный диск в фоновом процессе */

            BackgroundWorker mounter = new BackgroundWorker();

            mounter.DoWork += delegate
            {
                DokanOptions opt = new DokanOptions
                {
                    MountPoint = MountPoint,
                    DebugMode = false,
                    UseStdErr = false,
                    VolumeLabel = "RFS"
                };

                /* Загружаем DLL библиотеку и монтируем виртуальный диск */

                int status = DokanNet.DOKAN_ERROR;

                try
                {
                    status = DokanNet.DokanMain(opt, new RFS());
                }
                catch (DllNotFoundException e)
                {
                    Setup.Log("[drive]: Dokan library is not installed", e);
                    Success = false;
                    return;
                }

                /* Анализируем состояние смонтированного виртуального диска */

                switch (status)
                {
                    case DokanNet.DOKAN_DRIVE_LETTER_ERROR:
                        Setup.Log("[drive]: Drive letter error");
                        Success = false;
                        break;
                    case DokanNet.DOKAN_DRIVER_INSTALL_ERROR:
                        Setup.Log("[drive]: Driver install error");
                        Success = false;
                        break;
                    case DokanNet.DOKAN_MOUNT_ERROR:
                        Setup.Log("[drive]: Mount error");
                        Success = false;
                        break;
                    case DokanNet.DOKAN_START_ERROR:
                        Setup.Log("[drive]: Start error");
                        Success = false;
                        break;
                    case DokanNet.DOKAN_ERROR:
                        Setup.Log("[drive]: Unknown error");
                        Success = false;
                        break;
                    default:
                        Setup.Log("[drive]: Unknown status: " + status);
                        Success = false;
                        break;
                }
            };

            mounter.RunWorkerAsync();
        }

        /// <summary>
        /// Функция ищет первую свободную букву диска
        /// </summary>
        /// <returns>Первая свободная буква диска или DriveNotFoundException</returns>

        private static char getFirstAviableDriveLetter()
        {
            List<char> usedDrives = new List<char>();

            foreach (DriveInfo drive in DriveInfo.GetDrives())
                usedDrives.Add(drive.Name.ToUpperInvariant()[0]);
            usedDrives.Sort();

            char lastDrive = usedDrives[usedDrives.Count - 1];

            lastDrive ++;

            if (lastDrive >= 'Z')
                throw new DriveNotFoundException("No drives available");

            return lastDrive;
        }
    }
}
